import { createClient, SupabaseClient } from '@supabase/supabase-js';
import winston from 'winston';
import { v4 as uuidv4 } from 'uuid';

export interface EmailPreferences {
  userId: string;
  emailEnabled: boolean;
  frequency: 'immediate' | 'daily' | 'weekly' | 'monthly' | 'never';
  digestTypes: {
    daily: boolean;
    weekly: boolean;
    paperAlerts: boolean;
    authorUpdates: boolean;
    trendingTopics: boolean;
    welcomeSeries: boolean;
  };
  categories: string[];
  keywords: string[];
  authors: string[];
  institutions: string[];
  quietHours: {
    enabled: boolean;
    start: string; // HH:MM format
    end: string;   // HH:MM format
    timezone: string;
  };
  deliverySettings: {
    maxPapersPerDigest: number;
    minRelevanceScore: number;
    includeTrendingPapers: boolean;
    includeDiscoveryPapers: boolean;
  };
  unsubscribeReasons?: string[];
  lastUpdated: Date;
}

export interface UnsubscribeToken {
  userId: string;
  email: string;
  digestId?: string;
  tokenType: 'global' | 'digest_specific' | 'category_specific';
  expiresAt: Date;
  metadata?: Record<string, any>;
}

export interface PreferenceUpdateHistory {
  userId: string;
  changeType: 'subscription' | 'unsubscription' | 'frequency_change' | 'category_change';
  oldValues: Record<string, any>;
  newValues: Record<string, any>;
  source: 'user_dashboard' | 'email_link' | 'api' | 'admin';
  timestamp: Date;
  ipAddress?: string;
  userAgent?: string;
}

export class EmailPreferenceService {
  private supabase: SupabaseClient;
  private logger: winston.Logger;

  constructor(supabaseUrl: string, supabaseKey: string, logger: winston.Logger) {
    this.supabase = createClient(supabaseUrl, supabaseKey);
    this.logger = logger;
  }

  /**
   * Get user email preferences
   */
  async getUserPreferences(userId: string): Promise<EmailPreferences | null> {
    try {
      // Get user info
      const { data: user, error: userError } = await this.supabase
        .from('users')
        .select('email, timezone, updated_at')
        .eq('user_id', userId)
        .single();

      if (userError || !user) {
        throw new Error(`User not found: ${userId}`);
      }

      // Get notification preferences
      const { data: notificationPrefs } = await this.supabase
        .from('notification_preferences')
        .select('*')
        .eq('user_id', userId);

      // Get user preferences
      const { data: userPrefs } = await this.supabase
        .from('user_preferences')
        .select('*')
        .eq('user_id', userId);

      // Get active subscriptions
      const { data: subscriptions } = await this.supabase
        .from('user_subscriptions')
        .select('*')
        .eq('user_id', userId)
        .eq('is_active', true);

      // Build comprehensive preferences object
      const preferences: EmailPreferences = {
        userId,
        emailEnabled: this.getNotificationSetting(notificationPrefs, 'email_digest', 'email', true),
        frequency: this.getPreferenceValue(userPrefs, 'email_frequency', 'daily'),
        digestTypes: {
          daily: this.getNotificationSetting(notificationPrefs, 'daily_digest', 'email', true),
          weekly: this.getNotificationSetting(notificationPrefs, 'weekly_summary', 'email', true),
          paperAlerts: this.getNotificationSetting(notificationPrefs, 'paper_alert', 'email', true),
          authorUpdates: this.getNotificationSetting(notificationPrefs, 'author_update', 'email', true),
          trendingTopics: this.getNotificationSetting(notificationPrefs, 'trending_topics', 'email', true),
          welcomeSeries: this.getNotificationSetting(notificationPrefs, 'welcome_series', 'email', true)
        },
        categories: this.getSubscriptionTargets(subscriptions, 'category'),
        keywords: this.getSubscriptionTargets(subscriptions, 'keyword'),
        authors: this.getSubscriptionTargets(subscriptions, 'author'),
        institutions: this.getSubscriptionTargets(subscriptions, 'institution'),
        quietHours: {
          enabled: this.getPreferenceValue(userPrefs, 'quiet_hours_enabled', false),
          start: this.getPreferenceValue(userPrefs, 'quiet_hours_start', '22:00'),
          end: this.getPreferenceValue(userPrefs, 'quiet_hours_end', '08:00'),
          timezone: user.timezone || 'UTC'
        },
        deliverySettings: {
          maxPapersPerDigest: this.getPreferenceValue(userPrefs, 'max_papers_per_digest', 10),
          minRelevanceScore: this.getPreferenceValue(userPrefs, 'min_relevance_score', 0.5),
          includeTrendingPapers: this.getPreferenceValue(userPrefs, 'include_trending', true),
          includeDiscoveryPapers: this.getPreferenceValue(userPrefs, 'include_discovery', true)
        },
        lastUpdated: new Date(user.updated_at)
      };

      return preferences;
    } catch (error) {
      this.logger.error(`Failed to get user preferences for ${userId}:`, error);
      return null;
    }
  }

  /**
   * Update user email preferences
   */
  async updateUserPreferences(
    userId: string,
    preferences: Partial<EmailPreferences>,
    source: string = 'user_dashboard',
    metadata: { ipAddress?: string; userAgent?: string } = {}
  ): Promise<{ success: boolean; error?: string }> {
    try {
      // Get current preferences for history tracking
      const currentPrefs = await this.getUserPreferences(userId);

      // Update notification preferences
      if (preferences.emailEnabled !== undefined || preferences.digestTypes) {
        await this.updateNotificationPreferences(userId, {
          emailEnabled: preferences.emailEnabled,
          digestTypes: preferences.digestTypes
        });
      }

      // Update user preferences
      if (preferences.frequency || preferences.quietHours || preferences.deliverySettings) {
        await this.updateUserPreferenceSettings(userId, {
          frequency: preferences.frequency,
          quietHours: preferences.quietHours,
          deliverySettings: preferences.deliverySettings
        });
      }

      // Update subscriptions
      if (preferences.categories || preferences.keywords || preferences.authors || preferences.institutions) {
        await this.updateUserSubscriptions(userId, {
          categories: preferences.categories,
          keywords: preferences.keywords,
          authors: preferences.authors,
          institutions: preferences.institutions
        });
      }

      // Record preference change history
      await this.recordPreferenceChange(userId, {
        changeType: 'preference_update',
        oldValues: currentPrefs ? this.serializePreferences(currentPrefs) : {},
        newValues: preferences,
        source,
        ipAddress: metadata.ipAddress,
        userAgent: metadata.userAgent
      });

      this.logger.info(`User preferences updated: ${userId} via ${source}`);
      return { success: true };

    } catch (error) {
      this.logger.error(`Failed to update user preferences for ${userId}:`, error);
      return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
    }
  }

  /**
   * Subscribe user to email notifications
   */
  async subscribeUser(
    userId: string,
    preferences: Partial<EmailPreferences> = {},
    source: string = 'user_action'
  ): Promise<{ success: boolean; error?: string }> {
    try {
      // Enable email notifications
      await this.supabase
        .from('notification_preferences')
        .upsert({
          user_id: userId,
          notification_type: 'email_digest',
          delivery_method: 'email',
          is_enabled: true,
          frequency: preferences.frequency || 'daily',
          updated_at: new Date().toISOString()
        });

      // Set default digest types if not specified
      const defaultDigestTypes = preferences.digestTypes || {
        daily: true,
        weekly: false,
        paperAlerts: true,
        authorUpdates: true,
        trendingTopics: false,
        welcomeSeries: true
      };

      // Enable specific digest types
      for (const [digestType, enabled] of Object.entries(defaultDigestTypes)) {
        if (enabled) {
          await this.supabase
            .from('notification_preferences')
            .upsert({
              user_id: userId,
              notification_type: digestType,
              delivery_method: 'email',
              is_enabled: true,
              updated_at: new Date().toISOString()
            });
        }
      }

      // Add default subscriptions if provided
      if (preferences.categories && preferences.categories.length > 0) {
        await this.addSubscriptions(userId, 'category', preferences.categories);
      }

      if (preferences.keywords && preferences.keywords.length > 0) {
        await this.addSubscriptions(userId, 'keyword', preferences.keywords);
      }

      // Record subscription event
      await this.recordPreferenceChange(userId, {
        changeType: 'subscription',
        oldValues: { emailEnabled: false },
        newValues: { emailEnabled: true, ...preferences },
        source
      });

      this.logger.info(`User subscribed to email notifications: ${userId}`);
      return { success: true };

    } catch (error) {
      this.logger.error(`Failed to subscribe user ${userId}:`, error);
      return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
    }
  }

  /**
   * Unsubscribe user from email notifications
   */
  async unsubscribeUser(
    userId: string,
    unsubscribeType: 'global' | 'digest_specific' | 'category_specific' = 'global',
    specificTarget?: string,
    reasons: string[] = [],
    source: string = 'email_link'
  ): Promise<{ success: boolean; error?: string }> {
    try {
      if (unsubscribeType === 'global') {
        // Disable all email notifications
        await this.supabase
          .from('notification_preferences')
          .update({ 
            is_enabled: false,
            updated_at: new Date().toISOString()
          })
          .eq('user_id', userId)
          .eq('delivery_method', 'email');

        // Deactivate all subscriptions
        await this.supabase
          .from('user_subscriptions')
          .update({ 
            is_active: false,
            updated_at: new Date().toISOString()
          })
          .eq('user_id', userId);

      } else if (unsubscribeType === 'digest_specific' && specificTarget) {
        // Disable specific digest type
        await this.supabase
          .from('notification_preferences')
          .update({ 
            is_enabled: false,
            updated_at: new Date().toISOString()
          })
          .eq('user_id', userId)
          .eq('notification_type', specificTarget)
          .eq('delivery_method', 'email');

      } else if (unsubscribeType === 'category_specific' && specificTarget) {
        // Remove specific category subscription
        await this.supabase
          .from('user_subscriptions')
          .update({ 
            is_active: false,
            updated_at: new Date().toISOString()
          })
          .eq('user_id', userId)
          .eq('subscription_type', 'category')
          .eq('subscription_target', specificTarget);
      }

      // Store unsubscribe reasons
      if (reasons.length > 0) {
        await this.supabase
          .from('user_preferences')
          .upsert({
            user_id: userId,
            preference_key: 'unsubscribe_reasons',
            preference_value: reasons,
            updated_at: new Date().toISOString()
          });
      }

      // Record unsubscribe event
      await this.recordPreferenceChange(userId, {
        changeType: 'unsubscription',
        oldValues: { emailEnabled: true },
        newValues: { 
          emailEnabled: false, 
          unsubscribeType, 
          specificTarget, 
          reasons 
        },
        source
      });

      this.logger.info(`User unsubscribed: ${userId} (${unsubscribeType})`);
      return { success: true };

    } catch (error) {
      this.logger.error(`Failed to unsubscribe user ${userId}:`, error);
      return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
    }
  }

  /**
   * Generate unsubscribe token
   */
  async generateUnsubscribeToken(
    userId: string,
    tokenType: 'global' | 'digest_specific' | 'category_specific' = 'global',
    digestId?: string,
    specificTarget?: string,
    expirationDays: number = 30
  ): Promise<string> {
    try {
      const tokenId = uuidv4();
      const expiresAt = new Date();
      expiresAt.setDate(expiresAt.getDate() + expirationDays);

      // Get user email
      const { data: user } = await this.supabase
        .from('users')
        .select('email')
        .eq('user_id', userId)
        .single();

      // Store unsubscribe token
      await this.supabase
        .from('unsubscribe_tokens')
        .insert({
          token_id: tokenId,
          user_id: userId,
          email: user?.email,
          token_type: tokenType,
          digest_id: digestId,
          specific_target: specificTarget,
          expires_at: expiresAt.toISOString(),
          is_used: false,
          created_at: new Date().toISOString()
        });

      return tokenId;
    } catch (error) {
      this.logger.error(`Failed to generate unsubscribe token for ${userId}:`, error);
      throw error;
    }
  }

  /**
   * Process unsubscribe using token
   */
  async processUnsubscribeToken(
    token: string,
    reasons: string[] = []
  ): Promise<{ 
    success: boolean; 
    userId?: string; 
    email?: string; 
    tokenType?: string;
    error?: string;
  }> {
    try {
      // Get and validate token
      const { data: tokenData, error: tokenError } = await this.supabase
        .from('unsubscribe_tokens')
        .select('*')
        .eq('token_id', token)
        .eq('is_used', false)
        .single();

      if (tokenError || !tokenData) {
        return { success: false, error: 'Invalid or expired unsubscribe token' };
      }

      // Check if token is expired
      if (new Date(tokenData.expires_at) < new Date()) {
        return { success: false, error: 'Unsubscribe token has expired' };
      }

      // Mark token as used
      await this.supabase
        .from('unsubscribe_tokens')
        .update({ 
          is_used: true,
          used_at: new Date().toISOString()
        })
        .eq('token_id', token);

      // Process unsubscribe
      const unsubscribeResult = await this.unsubscribeUser(
        tokenData.user_id,
        tokenData.token_type as any,
        tokenData.specific_target,
        reasons,
        'email_link'
      );

      if (!unsubscribeResult.success) {
        return { 
          success: false, 
          error: unsubscribeResult.error,
          userId: tokenData.user_id,
          email: tokenData.email
        };
      }

      return {
        success: true,
        userId: tokenData.user_id,
        email: tokenData.email,
        tokenType: tokenData.token_type
      };

    } catch (error) {
      this.logger.error(`Failed to process unsubscribe token ${token}:`, error);
      return { success: false, error: 'Failed to process unsubscribe request' };
    }
  }

  /**
   * Resubscribe user
   */
  async resubscribeUser(
    userId: string,
    preferences?: Partial<EmailPreferences>
  ): Promise<{ success: boolean; error?: string }> {
    try {
      // Check if user exists and get current status
      const currentPrefs = await this.getUserPreferences(userId);
      if (!currentPrefs) {
        return { success: false, error: 'User not found' };
      }

      // Re-enable email notifications
      const subscribeResult = await this.subscribeUser(
        userId,
        preferences,
        'resubscribe'
      );

      if (subscribeResult.success) {
        // Clear unsubscribe reasons
        await this.supabase
          .from('user_preferences')
          .delete()
          .eq('user_id', userId)
          .eq('preference_key', 'unsubscribe_reasons');

        this.logger.info(`User resubscribed: ${userId}`);
      }

      return subscribeResult;
    } catch (error) {
      this.logger.error(`Failed to resubscribe user ${userId}:`, error);
      return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
    }
  }

  /**
   * Get user preference history
   */
  async getPreferenceHistory(
    userId: string,
    limit: number = 50
  ): Promise<PreferenceUpdateHistory[]> {
    try {
      const { data, error } = await this.supabase
        .from('user_preference_history')
        .select('*')
        .eq('user_id', userId)
        .order('timestamp', { ascending: false })
        .limit(limit);

      if (error) {
        throw error;
      }

      return data.map(record => ({
        userId: record.user_id,
        changeType: record.change_type,
        oldValues: record.old_values || {},
        newValues: record.new_values || {},
        source: record.source,
        timestamp: new Date(record.timestamp),
        ipAddress: record.ip_address,
        userAgent: record.user_agent
      }));

    } catch (error) {
      this.logger.error(`Failed to get preference history for ${userId}:`, error);
      return [];
    }
  }

  /**
   * Get preference analytics
   */
  async getPreferenceAnalytics(
    startDate: Date,
    endDate: Date
  ): Promise<{
    totalSubscriptions: number;
    totalUnsubscriptions: number;
    netSubscriptions: number;
    subscriptionRate: number;
    unsubscribeRate: number;
    topUnsubscribeReasons: Array<{ reason: string; count: number }>;
    frequencyDistribution: Record<string, number>;
    categoryPopularity: Array<{ category: string; subscribers: number }>;
  }> {
    try {
      // Get subscription/unsubscription events
      const { data: events } = await this.supabase
        .from('user_preference_history')
        .select('change_type, old_values, new_values, timestamp')
        .gte('timestamp', startDate.toISOString())
        .lte('timestamp', endDate.toISOString());

      let totalSubscriptions = 0;
      let totalUnsubscriptions = 0;
      const frequencyDist: Record<string, number> = {};
      const unsubscribeReasons: string[] = [];

      events?.forEach(event => {
        if (event.change_type === 'subscription') {
          totalSubscriptions++;
        } else if (event.change_type === 'unsubscription') {
          totalUnsubscriptions++;
          if (event.new_values?.reasons) {
            unsubscribeReasons.push(...event.new_values.reasons);
          }
        }

        if (event.new_values?.frequency) {
          frequencyDist[event.new_values.frequency] = (frequencyDist[event.new_values.frequency] || 0) + 1;
        }
      });

      // Get category popularity
      const { data: categoryData } = await this.supabase
        .from('user_subscriptions')
        .select('subscription_target')
        .eq('subscription_type', 'category')
        .eq('is_active', true);

      const categoryCount = new Map<string, number>();
      categoryData?.forEach(sub => {
        categoryCount.set(sub.subscription_target, (categoryCount.get(sub.subscription_target) || 0) + 1);
      });

      const categoryPopularity = Array.from(categoryCount.entries())
        .map(([category, count]) => ({ category, subscribers: count }))
        .sort((a, b) => b.subscribers - a.subscribers)
        .slice(0, 20);

      // Count unsubscribe reasons
      const reasonCount = new Map<string, number>();
      unsubscribeReasons.forEach(reason => {
        reasonCount.set(reason, (reasonCount.get(reason) || 0) + 1);
      });

      const topUnsubscribeReasons = Array.from(reasonCount.entries())
        .map(([reason, count]) => ({ reason, count }))
        .sort((a, b) => b.count - a.count)
        .slice(0, 10);

      const netSubscriptions = totalSubscriptions - totalUnsubscriptions;
      const totalEvents = totalSubscriptions + totalUnsubscriptions;

      return {
        totalSubscriptions,
        totalUnsubscriptions,
        netSubscriptions,
        subscriptionRate: totalEvents > 0 ? (totalSubscriptions / totalEvents) * 100 : 0,
        unsubscribeRate: totalEvents > 0 ? (totalUnsubscriptions / totalEvents) * 100 : 0,
        topUnsubscribeReasons,
        frequencyDistribution: frequencyDist,
        categoryPopularity
      };

    } catch (error) {
      this.logger.error('Failed to get preference analytics:', error);
      throw error;
    }
  }

  /**
   * Helper methods
   */
  private getNotificationSetting(
    preferences: any[],
    notificationType: string,
    deliveryMethod: string,
    defaultValue: boolean
  ): boolean {
    const pref = preferences?.find(p => 
      p.notification_type === notificationType && 
      p.delivery_method === deliveryMethod
    );
    return pref ? pref.is_enabled : defaultValue;
  }

  private getPreferenceValue(preferences: any[], key: string, defaultValue: any): any {
    const pref = preferences?.find(p => p.preference_key === key);
    return pref ? pref.preference_value : defaultValue;
  }

  private getSubscriptionTargets(subscriptions: any[], type: string): string[] {
    return subscriptions
      ?.filter(sub => sub.subscription_type === type && sub.is_active)
      .map(sub => sub.subscription_target) || [];
  }

  private async updateNotificationPreferences(
    userId: string,
    preferences: {
      emailEnabled?: boolean;
      digestTypes?: Partial<EmailPreferences['digestTypes']>;
    }
  ): Promise<void> {
    if (preferences.emailEnabled !== undefined) {
      await this.supabase
        .from('notification_preferences')
        .upsert({
          user_id: userId,
          notification_type: 'email_digest',
          delivery_method: 'email',
          is_enabled: preferences.emailEnabled,
          updated_at: new Date().toISOString()
        });
    }

    if (preferences.digestTypes) {
      for (const [digestType, enabled] of Object.entries(preferences.digestTypes)) {
        if (enabled !== undefined) {
          await this.supabase
            .from('notification_preferences')
            .upsert({
              user_id: userId,
              notification_type: digestType,
              delivery_method: 'email',
              is_enabled: enabled,
              updated_at: new Date().toISOString()
            });
        }
      }
    }
  }

  private async updateUserPreferenceSettings(
    userId: string,
    preferences: {
      frequency?: string;
      quietHours?: Partial<EmailPreferences['quietHours']>;
      deliverySettings?: Partial<EmailPreferences['deliverySettings']>;
    }
  ): Promise<void> {
    const updates: Array<{ key: string; value: any }> = [];

    if (preferences.frequency) {
      updates.push({ key: 'email_frequency', value: preferences.frequency });
    }

    if (preferences.quietHours) {
      if (preferences.quietHours.enabled !== undefined) {
        updates.push({ key: 'quiet_hours_enabled', value: preferences.quietHours.enabled });
      }
      if (preferences.quietHours.start) {
        updates.push({ key: 'quiet_hours_start', value: preferences.quietHours.start });
      }
      if (preferences.quietHours.end) {
        updates.push({ key: 'quiet_hours_end', value: preferences.quietHours.end });
      }
    }

    if (preferences.deliverySettings) {
      Object.entries(preferences.deliverySettings).forEach(([key, value]) => {
        if (value !== undefined) {
          updates.push({ key: `delivery_${key}`, value });
        }
      });
    }

    // Batch update preferences
    for (const update of updates) {
      await this.supabase
        .from('user_preferences')
        .upsert({
          user_id: userId,
          preference_key: update.key,
          preference_value: update.value,
          updated_at: new Date().toISOString()
        });
    }
  }

  private async updateUserSubscriptions(
    userId: string,
    subscriptions: {
      categories?: string[];
      keywords?: string[];
      authors?: string[];
      institutions?: string[];
    }
  ): Promise<void> {
    for (const [type, targets] of Object.entries(subscriptions)) {
      if (targets && Array.isArray(targets)) {
        const subscriptionType = type.slice(0, -1); // Remove 's' from end

        // Deactivate existing subscriptions of this type
        await this.supabase
          .from('user_subscriptions')
          .update({ is_active: false })
          .eq('user_id', userId)
          .eq('subscription_type', subscriptionType);

        // Add new subscriptions
        await this.addSubscriptions(userId, subscriptionType, targets);
      }
    }
  }

  private async addSubscriptions(
    userId: string,
    subscriptionType: string,
    targets: string[]
  ): Promise<void> {
    const subscriptions = targets.map(target => ({
      user_id: userId,
      subscription_type: subscriptionType,
      subscription_target: target,
      is_active: true,
      notification_frequency: 'daily',
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    }));

    await this.supabase
      .from('user_subscriptions')
      .upsert(subscriptions, { onConflict: 'user_id,subscription_type,subscription_target' });
  }

  private async recordPreferenceChange(
    userId: string,
    change: Omit<PreferenceUpdateHistory, 'userId' | 'timestamp'>
  ): Promise<void> {
    try {
      await this.supabase
        .from('user_preference_history')
        .insert({
          user_id: userId,
          change_type: change.changeType,
          old_values: change.oldValues,
          new_values: change.newValues,
          source: change.source,
          ip_address: change.ipAddress,
          user_agent: change.userAgent,
          timestamp: new Date().toISOString()
        });
    } catch (error) {
      this.logger.error('Failed to record preference change:', error);
      // Don't throw - this is for audit purposes only
    }
  }

  private serializePreferences(preferences: EmailPreferences): Record<string, any> {
    return {
      emailEnabled: preferences.emailEnabled,
      frequency: preferences.frequency,
      digestTypes: preferences.digestTypes,
      categories: preferences.categories,
      keywords: preferences.keywords,
      authors: preferences.authors,
      institutions: preferences.institutions,
      quietHours: preferences.quietHours,
      deliverySettings: preferences.deliverySettings
    };
  }
}